﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Valhalla.Emulator
{
    public class CPU
    {
        public GBCart rom;
        int A, B, C, D, E, F, H, L;
        int PC, SP;
        //short AF, BC, DE, HL;

        byte[] videoRam = new byte[8 * 1024];
        byte[] highRam = new byte[256];
        byte[] spritesRam = new byte[256];
        byte[] myLittleRam = new byte[8 * 1024];
        public int tick = 0;
        public CPU(GBCart gbc)
        {
            rom = gbc;
        }

        public void startUp()
        {
            //set proper values
            reset();
        }



        void reset()
        {
            A = B = C = D = E = F = H = L = 0;
            SP = 0;
            PC = 100;
        }

        public void doStep()
        {
            //TODO:
            //interrupts maybe?
            PC &= 0xFFFF;
            int curOptCode = ReadByte(PC);
            Console.WriteLine(curOptCode);
            switch (curOptCode)
            {
                case 0x04:
                    inc(ref B);
                    break;
                case 0x05:
                    dec(ref B);
                    break;
                case 0x0C:
                    inc(ref C);
                    break;
                case 0x0D:
                    dec(ref C);
                    break;
                case 0x14:
                    inc(ref D);
                    break;
                case 0x15:
                    dec(ref D);
                    break;
                case 0x1C:
                    inc(ref E);
                    break;
                case 0x1D:
                    dec(ref E);
                    break;
                case 0x24:
                    inc(ref H);
                    break;
                case 0x25:
                    dec(ref H);
                    break;
                case 0x2C:
                    inc(ref L);
                    break;
                case 0x2D:
                    dec(ref L);
                    break;
                case 0x34:
                    NOP(); //inc (HL)
                    break;
                case 0x35:
                    NOP(); //dec (HL)
                    break;
                case 0x3C:
                    inc(ref A);
                    break;
                case 0x3D:
                    dec(ref A);
                    break;
                case 0x3F:
                    ccf();
                    break;
                case 0x80:
                    add(B);
                    break;
                case 0x81:
                    add(C);
                    break;
                case 0x82:
                    add(D);
                    break;
                case 0x83:
                    add(E);
                    break;
                case 0x84:
                    add(H);
                    break;
                case 0x85:
                    add(L);
                    break;
                case 0x86:
                    NOP(); //add a,(hl)
                    break;
                case 0x87:
                    add(A);
                    break;
                case 0x88:
                    adc(B);
                    break;
                case 0x89:
                    adc(C);
                    break;
                case 0x8A:
                    adc(D);
                    break;
                case 0x8B:
                    adc(E);
                    break;
                case 0x8C:
                    adc(H);
                    break;
                case 0x8D:
                    adc(L);
                    break;
                case 0x8E:
                    NOP(); //adc A, (HL)
                    break;
                case 0x8F:
                    adc(A);
                    break;
                case 0x90:
                    sub(B);
                    break;
                case 0x91:
                    sub(C);
                    break;
                case 0x92:
                    sub(D);
                    break;
                case 0x93:
                    sub(E);
                    break;
                case 0x94:
                    sub(H);
                    break;
                case 0x95:
                    sub(L);
                    break;
                case 0x96:
                    NOP(); //sub A,(HL)
                    break;
                case 0x97:
                    sub(A);
                    break;
                case 0xA0:
                    and(B);
                    break;
                case 0xA1:
                    and(C);
                    break;
                case 0xA2:
                    and(D);
                    break;
                case 0xA3:
                    and(E);
                    break;
                case 0xA4:
                    and(H);
                    break;
                case 0xA5:
                    and(L);
                    break;
                case 0xA6:
                    NOP(); //and A (HL)
                    break;
                case 0xA7:
                    and(A);
                    break;
                case 0xA8:
                    xor(B);
                    break;
                case 0xA9:
                    xor(C);
                    break;
                case 0xAA:
                    xor(D);
                    break;
                case 0xAB:
                    xor(E);
                    break;
                case 0xAC:
                    xor(H);
                    break;
                case 0xAD:
                    xor(L);
                    break;
                case 0xAE:
                    NOP(); //xor A (HL) 
                    break;
                case 0xAF:
                    xor(A);
                    break;
                case 0xB0:
                    or(B);
                    break;
                case 0xB1:
                    or(C);
                    break;
                case 0xB2:
                    or(D);
                    break;
                case 0xB3:
                    or(E);
                    break;
                case 0xB4:
                    or(H);
                    break;
                case 0xB5:
                    or(L);
                    break;
                case 0xB6:
                    NOP(); //or A (HL)
                    break;
                case 0xB7:
                    or(A);
                    break;
                case 0xB8:
                    cp(B);
                    break;
                case 0xB9:
                    cp(C);
                    break;
                case 0xBA:
                    cp(D);
                    break;
                case 0xBB:
                    cp(E);
                    break;
                case 0xBC:
                    cp(H);
                    break;
                case 0xBD:
                    cp(L);
                    break;
                case 0xBE:
                    NOP(); //compare A (HL)
                    break;
                case 0xBF:
                    cp(A);
                    break;
                case 0xCB:
                        byte secondByteOfOptCode = ReadByte(PC++);
                        switch (secondByteOfOptCode)
                        {
                            case 0x30:
                                swap(ref B);
                                break;
                            case 0x31:
                                swap(ref C);
                                break;
                            case 0x32:
                                swap(ref D);
                                break;
                            case 0x33:
                                swap(ref E);
                                break;
                            case 0x34:
                                swap(ref H);
                                break;
                            case 0x35:
                                swap(ref L);
                                break;
                            case 0x36:
                                NOP(); //swap (HL)
                                break;
                            case 0x37:
                                swap(ref A);
                                break;
                            default:
                                NOP();
                                break;
                        }
                        break;
                default:
                    NOP();
                    break;
            }
            PC++;
        }

        void NOP()
        { t4(); }

        void and(int x)
        {
            A = 0xFF & (A & x);
            Zflag = A == 0;
            Nflag = false;
            Hflag = true;
            Cflag = false;
            t4();
        }

        void or(int x)
        {
            A = 0xFF & (A | x);
            Zflag = A == 0;
            Nflag = false;
            Hflag = false;
            Cflag = false;
            t4();
        }

        void xor(int x)
        {
            A = 0xFF & (A ^ x);
            Zflag = A == 0;
            Nflag = false;
            Hflag = false;
            Cflag = false;
            t4();
        }

        void cp(int x)
        {
            Zflag = A == x;
            Nflag = true;
            Hflag = (A & 0x0F) < (x & 0x0F);
            Cflag = x > A;
            t4();
        }

        void inc(ref int reg)
        {
            Hflag = (reg & 0x0F) == 0x0F;
            reg++;
            reg &= 0xFF;
            Nflag = false;
            Zflag = reg == 0;
            t4();
        }

        void dec(ref int reg)
        {
            Hflag = (reg & 0x0F) == 0x00;
            reg--;
            reg &= 0xFF;
            Nflag = true;
            Zflag = reg == 0;
            t4();
        }
        
        void ccf()
        {
            Cflag = !Cflag;
            Nflag = false;
            Hflag = false;
            t4();
        }

        void swap(ref int reg)
        {
            reg = 0xFF & ((reg << 4) | (reg >> 4));
            Zflag = reg == 0;
            Nflag = false;
            Hflag = false;
            Cflag = false;
            t8();
        }

        void add(int x)
        {
            Hflag = (A & 0x0F) + (x & 0x0F) > 0x0F;
            A += x;
            Zflag = A == 0;
            Nflag = false;
            Cflag = A > 0xFF;
            A &= 0xFF;
            t4();
        }

        void adc(int x)
        {
            int cr = Cflag ? 1 : 0;
            Hflag = cr + (A & 0x0F) + (x & 0x0F) > 0x0F;
            A += x + cr;
            Cflag = A > 0xFF;
            A &= 0xFF;
            Zflag = A == 0;
            Nflag = false;
            t4();
        }

        void sub(int x)
        {
            Hflag = (A & 0x0F) < (x & 0x0F);
            Cflag = x < A;
            A -= x;
            A &= 0xFF;
            Zflag = A == 0;
            Nflag = true;
            t4();
        }

        void t4()
        { tick += 4; }
        void t8()
        { tick += 8; }

        bool Zflag
        {
            get { return checkBit(F, 0); }
            set
            {
                if (value == true)
                    F = setBit(F, 0);
                else
                    F = unsetBit(F, 0);
            }
        }
        bool Nflag
        {
            get { return checkBit(F, 1); }
            set
            {
                if (value == true)
                    F = setBit(F, 1);
                else
                    F = unsetBit(F, 1);
            }
        }
        bool Hflag
        {
            get { return checkBit(F, 2); }
            set
            {
                if (value == true)
                    F = setBit(F, 2);
                else
                    F = unsetBit(F, 2);
            }
        }
        bool Cflag
        {
            get { return checkBit(F, 3); }
            set
            {
                if (value == true)
                    F = setBit(F, 3);
                else
                    F = unsetBit(F, 3);
            }
        }

        byte ReadByte(int offset)
        { 
               if (offset <= 0x7FFF || (offset >= 0xA000 && offset <= 0xBFFF)) {
                   return rom.ReadByte(offset); } 
               else if (offset >= 0x8000 && offset <= 0x9FFF) 
               { return videoRam[offset - 0x8000]; } 
               else if (offset >= 0xC000 && offset <= 0xDFFF) 
               { return myLittleRam[offset - 0xC000]; } 
               else if (offset >= 0xE000 && offset <= 0xFDFF) 
               { return myLittleRam[offset - 0xE000]; } 
               else if (offset >= 0xFE00 && offset <= 0xFEFF) 
               { return spritesRam[offset - 0xFE00]; } 
               else if (offset >= 0xFF80 && offset <= 0xFFFE) 
               { return highRam[0xFF & offset];} 
               else { return 0; }
            /*
            [FF00-FF7F] Memory-mapped I/O: Each of the GameBoy's subsystems (graphics, sound, etc.) has control values, 
             * to allow programs to create effects and use the hardware. These values are available to the CPU directly 
             * on the address bus, in this area.
            [FF80-FFFF] Zero-page RAM: A high-speed area of 128 bytes of RAM is available at the top of memory.
             * Oddly, though this is "page" 255 of the memory, it is referred to as page zero, since most of the 
             * interaction between the program and the GameBoy hardware occurs through use of this page of memory.
             */
        }
        int setBit(int register, int x)
        { return register |= 1 << x; }
        int unsetBit(int register, int x)
        { return register &= ~(1 << x); }
        bool checkBit(int register, int x)
        {
            if ((register & (1 << x)) == 1)
            { return true; }
            return false;
        }
    }
}
